package com.cmc.cloud.cmclink.doc.service.impl.si;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cmc.cloud.cmclink.bdt.api.base.FinanceItemApi;
import com.cmc.cloud.cmclink.bdt.api.base.dto.req.FinanceItemListByCodeReqVO;
import com.cmc.cloud.cmclink.bdt.api.base.dto.resp.FinanceItemRespDTO;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.constants.LocalChargeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoLocalChargeErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.BlnoFreightConvert;
import com.cmc.cloud.cmclink.doc.convert.BlnoLocalChargeConvert;
import com.cmc.cloud.cmclink.doc.convert.BlnoLocalChargeDetailConvert;
import com.cmc.cloud.cmclink.doc.dto.BlnoLocalChargeDto;
import com.cmc.cloud.cmclink.doc.dto.ImExVslvoyAge;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.mapper.*;
import com.cmc.cloud.cmclink.doc.rpcservice.CustomerRpcService;
import com.cmc.cloud.cmclink.doc.rpcservice.RateTransfer;
import com.cmc.cloud.cmclink.doc.service.BlnoCalculateOperateService;
import com.cmc.cloud.cmclink.doc.service.BlnoCntrService;
import com.cmc.cloud.cmclink.doc.service.si.*;
import com.cmc.cloud.cmclink.doc.util.CostChargeUtil;
import com.cmc.cloud.cmclink.doc.util.I18nsUtil;
import com.cmc.cloud.cmclink.doc.util.RedisUtils;
import com.cmc.cloud.cmclink.doc.util.sepcial.CommonUtil;
import com.cmc.cloud.cmclink.doc.vo.blnochargevo.BlnoLocalChargeResultVO;
import com.cmc.cloud.cmclink.doc.vo.blnochargevo.BlnoLocalChargeUpdateReqVO;
import com.cmc.cloud.cmclink.doc.vo.blnolocalchargevo.*;
import com.cmc.cloud.cmclink.fin.api.DocBlnoApi;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoBaseReqDTO;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoCreateReqDTO;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoFreightInfoReqDTO;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoVslvoyReqDTO;
import com.cmc.cloud.cmclink.mkt.dto.loc.CalLocRateUnitReqVo;
import com.cmc.cloud.cmclink.mkt.dto.loc.CalLocRateUnitRespVo;
import com.cmc.cloud.cmclink.mkt.dto.loc.CalLocReqVo;
import com.cmc.cloud.cmclink.mkt.dto.loc.CalLocRespVo;
import com.cmc.cloud.cmclink.mkt.dto.rate.FeeRateVo;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.CommonResult;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cmes.framework.security.core.util.SecurityFrameworkUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.cmes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;

/**
 * Local Charge费用 Service 实现类
 *
 * @author 集运管理员
 */
@Slf4j
@Service
@Validated
public class BlnoLocalChargeServiceImpl implements BlnoLocalChargeService {

    private static final String GET_CNTR_TYPE_EXCEPTION = "local charge计费发送财务失败";

    @Value("${local.charge.redisTimeOut}")
    private Long redisTimeOut;

    @Resource
    private DocBlnoApi docBlnoApi;

    @Resource
    private BlnoLocalChargeMapper blnoLocalChargeMapper;

    @Resource
    private BlnoLocalChargeDetailMapper blnoLocalChargeDetailMapper;

    @Resource
    private FinanceItemApi financeItemApi;

    @Resource
    private BlnoMapper blnoMapper;

    @Resource
    private BlnoCntrMapper blnoCntrMapper;

    @Resource
    private RateTransfer rateTransfer;

    @Resource
    private BlnoCntrService blnoCntrService;

    @Resource
    private BlnoCargoService blnoCargoService;

    @Resource
    private BlnoVslvoyService blnoVslvoyService;

    @Resource
    private BlnoScnService blnoScnService;

    @Resource
    private BlnoPaymentService blnoPaymentService;

    @Resource
    private ManifestMapper manifestMapper;

    @Resource
    private BlnoCalculateOperateService blnoCalculateOperateService;

    @Resource
    private CalculateOperateLogMapper calculateOperateLogMapper;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private CustomerRpcService customerRpcService;

    @Resource
    private CostChargeUtil costChargeUtil;

    @Resource
    private I18nsUtil i18nsUtil;

    private static final String SUCCESS = "SUCCESS";

    /**
     * local charge 查询逻辑
     * 出口：目的港提单+pol查询过滤
     * 进口：目的港提单+pod查询过滤
     *
     * @param reqVo
     * @return
     */
    @Override
    public List<BlnoLocalChargeRespVO> getBlnoInfo(BlnoLocalChargeReqVO reqVo) {
        boolean check = StringUtils.isBlank(reqVo.getBlNo()) && (StringUtils.isBlank(reqVo.getBkgVesselCode()) || StringUtils.isBlank(reqVo.getBkgVoyage()));
        if (check) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_IS_NOT_ALL_NULL);
        }
        List<String> rejectStatuList = new ArrayList<>();
        rejectStatuList.add(SiStatusEnum.BookingDraft.getValue());
        rejectStatuList.add(SiStatusEnum.BookingProcessing.getValue());
        reqVo.setBlNoStatus(rejectStatuList);
        List<BlnoLocalChargeRespVO> blnoLocalChargeRespVoList = new ArrayList<>(128);
        if (EmImTypeEnum.EXPORT.getValue().equals(reqVo.getImExType())) {
            blnoLocalChargeRespVoList = blnoMapper.getBlnoInfo(reqVo);
        } else {
            blnoLocalChargeRespVoList = blnoMapper.getImBlnoInfo(reqVo);
        }
        if (CollectionUtils.isEmpty(blnoLocalChargeRespVoList)) {
            return Collections.emptyList();
        }

        List<String> codes = blnoLocalChargeRespVoList.stream().map(BlnoLocalChargeRespVO::getBkpCode).collect(Collectors.toList());
        Map<String, String> companyNameMap = customerRpcService.getCompanyNameMap(codes);
        for (BlnoLocalChargeRespVO blnoLocalChargeRespVO : blnoLocalChargeRespVoList) {
            if (StringUtils.isNotBlank(blnoLocalChargeRespVO.getBkpCode())) {
                blnoLocalChargeRespVO.setBkpName(companyNameMap.getOrDefault(blnoLocalChargeRespVO.getBkpCode(), ""));
            }
        }

        return blnoLocalChargeRespVoList;
    }

    @Override
    public List<BlnoLocalChargeDetailRespVO> getLocalChargeDetail(Long blId, String imExType) {
        List<BlnoLocalChargeDetailRespVO> blnoLocalChargeDetailRespVoList = new ArrayList<>(128);
        LambdaQueryWrapperX<BlnoLocalChargeDO> blnoLocalChargeDoLambdaQueryWrapper = new LambdaQueryWrapperX<>();
        blnoLocalChargeDoLambdaQueryWrapper.eqIfPresent(BlnoLocalChargeDO::getBlnoId, blId).eq(BlnoLocalChargeDO::getImExType, imExType)
                .orderByDesc(BlnoLocalChargeDO::getChargeCode)
                .orderByDesc(BlnoLocalChargeDO::getRedStatus);
        List<BlnoLocalChargeDO> blnoLocalChargeDoList = blnoLocalChargeMapper.selectList(blnoLocalChargeDoLambdaQueryWrapper);
        if (CollectionUtils.isEmpty(blnoLocalChargeDoList)) {
            return Collections.emptyList();
        }
        blnoLocalChargeDetailRespVoList = BlnoLocalChargeConvert.INSTANCE.convertBlnoLocalChargeDetailRespVo(blnoLocalChargeDoList);
        List<String> codes = blnoLocalChargeDoList.stream().map(BlnoLocalChargeDO::getPayer).collect(Collectors.toList());
        Map<String, String> companyNameMap = customerRpcService.getCompanyNameMap(codes);
        if (CollectionUtils.isNotEmpty(blnoLocalChargeDetailRespVoList)) {
            for (BlnoLocalChargeDetailRespVO blnoLocalChargeDetailRespVO : blnoLocalChargeDetailRespVoList) {
                if (StringUtils.isNotBlank(blnoLocalChargeDetailRespVO.getPayer())) {
                    blnoLocalChargeDetailRespVO.setPayerName(companyNameMap.getOrDefault(blnoLocalChargeDetailRespVO.getPayer(), ""));
                }
            }
        }
        return blnoLocalChargeDetailRespVoList;
    }

    @Override
    public List<BlnoLocalChargeDetailCntrRespVO> getLocalChargeDetailWithCntr(Long blId, String imExType) {
        LambdaQueryWrapperX<BlnoLocalChargeDetailDO> queryWrapper = new LambdaQueryWrapperX<>();
        queryWrapper.eqIfPresent(BlnoLocalChargeDetailDO::getBlnoId, blId).eq(BlnoLocalChargeDetailDO::getImExType, imExType)
                .orderByDesc(BlnoLocalChargeDetailDO::getChargeCode);
        List<BlnoLocalChargeDetailDO> blnoLocalChargeDoList = blnoLocalChargeDetailMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(blnoLocalChargeDoList)) {
            return Collections.emptyList();
        }

        // 设置供应商名称
        List<BlnoLocalChargeDetailCntrRespVO> localChargeVoList = BlnoLocalChargeDetailConvert.INSTANCE.convertDetailCntrRespList(blnoLocalChargeDoList);
        Set<String> codes = blnoLocalChargeDoList.stream().map(BlnoLocalChargeDetailDO::getPayer).filter(Objects::nonNull)
                .filter(name -> !name.isEmpty()).collect(Collectors.toSet());
        Map<String, String> companyNameMap = customerRpcService.getCompanyNameMap(new ArrayList<>(codes));
        localChargeVoList.forEach(cost -> cost.setPayerName(companyNameMap.getOrDefault(cost.getPayer(), "")));

        return localChargeVoList;
    }

    @Override
    public CalculateOperateLogDO getCalculateLogInfo(String blNo, String imExType) {
        String calculateType = null;
        if (ExImTypeEnum.EX.getValue().equals(imExType)) {
            calculateType = CalculateTypeEnum.CALCULATE_LOCAL_CHARGE_EX.getValue();
        } else {
            calculateType = CalculateTypeEnum.CALCULATE_LOCAL_CHARGE_IM.getValue();
        }
        LambdaQueryWrapperX<CalculateOperateLogDO> lambdaQueryWrapperX = new LambdaQueryWrapperX<>();
        lambdaQueryWrapperX.eqIfPresent(CalculateOperateLogDO::getBusinessNo, blNo)
                .eqIfPresent(CalculateOperateLogDO::getCalculateType, calculateType)
                .orderByDesc(CalculateOperateLogDO::getCreateTime).last(" limit 1");

        List<CalculateOperateLogDO> calculateOperateLogDoList = calculateOperateLogMapper.selectList(lambdaQueryWrapperX);
        if (CollectionUtils.isEmpty(calculateOperateLogDoList)) {
            return new CalculateOperateLogDO();
        }
        return calculateOperateLogDoList.get(0);
    }

    @Override
    public List<BlnoCntrDO> getBlNoCntrInfo(Long blId) {
        LambdaQueryWrapperX<BlnoCntrDO> blnoCntrDoLambdaQueryWrapperX = new LambdaQueryWrapperX<>();
        blnoCntrDoLambdaQueryWrapperX.eqIfPresent(BlnoCntrDO::getBlnoId, blId);
        List<BlnoCntrDO> blnoCntrDoList = blnoCntrMapper.selectList(blnoCntrDoLambdaQueryWrapperX);
        if (CollectionUtils.isEmpty(blnoCntrDoList)) {
            return Collections.emptyList();
        }
        return blnoCntrDoList;
    }

    /**
     * local charge 计算分为出口和进口两种,计算分为按票和按箱计算
     *
     * @param blIds
     * @param imExType
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<BlnoLocalChargeResultVO> calcauteLocalCharge(Collection<Long> blIds, String imExType, String currentPort, String codeMigration) {
        List<BlnoLocalChargeResultVO> blnoLocalChargeResultVos = new ArrayList<>(128);
        //TODO 是否同步调用计算local cost
        if (CollectionUtils.isEmpty(blIds)) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_IDS_NOT_ALLOW_NULL);
        }
        // 获取基础数据信息,如果主数据都没有查出来说明id都是无效直接返回,查询主数据时需要通过目的港提单和DO状态过滤
        List<BlnoDO> blnoDoList = new ArrayList<>(128);
        BlnoLocalChargeSearchVO blnoLocalChargeSeqrchVo = BlnoLocalChargeSearchVO.builder()
                .ids((List<Long>) blIds)
                .currentPort(currentPort).build();
        if (ExImTypeEnum.EX.getValue().equals(imExType)) {
            blnoDoList = blnoMapper.searchExBlnoInfo(blnoLocalChargeSeqrchVo);
        } else {
            blnoDoList = blnoMapper.searchImBlnoInfo(blnoLocalChargeSeqrchVo);
        }

        if (CollectionUtils.isEmpty(blnoDoList)) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_IDS_NOT_ALLOW_NULL);
        }
        // 根据查询结果获取有效的数据id,然后查询其他信息
        Collection<Long> calcuteIds = blnoDoList.stream().map(BlnoDO::getId).collect(Collectors.toList());
        Collection<Long> lockIds = new ArrayList<>(64);
        //加redis锁
        BlnoLocalChargeDto blnoLocalChargeDto = addRedis(blnoDoList, imExType);
        lockIds.addAll(blnoLocalChargeDto.getLockIds());
        blnoLocalChargeResultVos.addAll(blnoLocalChargeDto.getBlnoLocalChargeResultVoList());

        if (CollectionUtils.isNotEmpty(lockIds)) {
            calcuteIds.removeAll(lockIds);
        }
        if (CollectionUtils.isEmpty(calcuteIds)) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_BL_STATUS_CHARGE);
        }

        List<BlnoCntrDO> cntrDoList = blnoCntrService.getBlnoCntrByBlnoIds(calcuteIds);
        List<BlnoCargoDO> blnoCargoDoList = blnoCargoService.getBlnoCargoByBlnoIds(calcuteIds);
        List<BlnoVslvoyDO> blnoVslvoyDoList = blnoVslvoyService.getBlnoVslvoyBySiIdList(calcuteIds);
        List<BlnoScnDO> blnoScnDoList = blnoScnService.getBlnoScnByBlnoIdList(calcuteIds);
        // 按照blId构建Map避免多层循环
        Map<Long, List<BlnoDO>> blnoMap = blnoDoList.stream().collect(Collectors.groupingBy(BlnoDO::getId));
        Map<Long, List<BlnoCntrDO>> blnoCntrMap = cntrDoList.stream().collect(Collectors.groupingBy(BlnoCntrDO::getBlnoId));
        Map<Long, List<BlnoCargoDO>> blnoCargoMap = blnoCargoDoList.stream().collect(Collectors.groupingBy(BlnoCargoDO::getBlnoId));
        Map<Long, List<BlnoVslvoyDO>> blnoVslvoyMap = blnoVslvoyDoList.stream().collect(Collectors.groupingBy(BlnoVslvoyDO::getBlnoId));
        Map<Long, List<BlnoScnDO>> blnoScnMap = blnoScnDoList.stream().collect(Collectors.groupingBy(BlnoScnDO::getBlnoId));

        try {
            //调用营销接口查询询价,接口中的请求ID是提单主表的主键，所以不用处理返回参数和请求参数的对应关系
            List<CalLocRespVo> calLocRespVoList = calMktLocalCharge(blnoScnMap, blnoDoList, blnoCargoMap, blnoVslvoyMap, blnoMap, imExType, blnoCntrMap, currentPort);
            if (CollectionUtils.isEmpty(calLocRespVoList)) {
                throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_EXCEPTION);
            }
            //计算费用以及红冲
            blnoLocalChargeResultVos.addAll(this.calcute(blnoMap, blnoCntrMap, blnoCargoMap, calLocRespVoList, imExType, blnoVslvoyMap, currentPort));
        } catch (Exception e) {
            log.info(e.getMessage());
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_EXCEPTION);
        } finally {
            this.deleteRedis(calcuteIds, imExType);
        }

        //删除redis的锁
        this.deleteRedis(calcuteIds, imExType);
        return blnoLocalChargeResultVos;
    }

    private BlnoLocalChargeDto addRedis(List<BlnoDO> blnoDoList, String imExType) {
        Locale currentLocale = LocaleContextHolder.getLocale();
        Collection<Long> lockIds = new ArrayList<>(64);
        List<BlnoDO> lockBlnos = new ArrayList<>(16);
        List<BlnoLocalChargeResultVO> blnoLocalChargeResultVos = new ArrayList<>(128);
        BlnoLocalChargeDto blnoLocalChargeDto = new BlnoLocalChargeDto();
        if (ExImTypeEnum.EX.getValue().equals(imExType)) {
            for (BlnoDO blnoDO : blnoDoList) {
                if (redisUtils.checKeyExist(LocalChargeConstants.LOCALCHARGEREDISKEY_EX + blnoDO.getId())) {
                    BlnoLocalChargeResultVO blnoLocalChargeResultVO = BlnoLocalChargeResultVO.builder()
                            .blNo(blnoDO.getBlNo())
                            .type(CostTypeEnum.LOCAL_CHARGE.getValue())
                            .msg(i18nsUtil.getMessage(BlnoLocalChargeErrorCodeConstants.CALCUTE_OR_ACCOUNT.getCode(), currentLocale)).build();
                    blnoLocalChargeResultVos.add(blnoLocalChargeResultVO);
                    lockIds.add(blnoDO.getId());
                    lockBlnos.add(blnoDO);
                } else {
                    redisUtils.setStringTimeOutByMinute(LocalChargeConstants.LOCALCHARGEREDISKEY_EX + blnoDO.getId(), String.valueOf(blnoDO.getId()), redisTimeOut);
                }
            }
        } else {
            for (BlnoDO blnoDO : blnoDoList) {
                if (redisUtils.checKeyExist(LocalChargeConstants.LOCALCHARGEREDISKEY_IM + blnoDO.getId())) {
                    BlnoLocalChargeResultVO blnoLocalChargeResultVO = BlnoLocalChargeResultVO.builder()
                            .blNo(blnoDO.getBlNo())
                            .type(CostTypeEnum.LOCAL_CHARGE.getValue())
                            .msg(i18nsUtil.getMessage(BlnoLocalChargeErrorCodeConstants.CALCUTE_OR_ACCOUNT.getCode(), currentLocale)).build();
                    blnoLocalChargeResultVos.add(blnoLocalChargeResultVO);
                    lockIds.add(blnoDO.getId());
                    lockBlnos.add(blnoDO);
                } else {
                    redisUtils.setStringTimeOutByMinute(LocalChargeConstants.LOCALCHARGEREDISKEY_IM + blnoDO.getId(), String.valueOf(blnoDO.getId()), redisTimeOut);
                }
            }
        }
        if (CollectionUtils.isNotEmpty(lockBlnos)) {
            blnoDoList.removeAll(lockBlnos);
        }
        blnoLocalChargeDto.setBlnoLocalChargeResultVoList(blnoLocalChargeResultVos);
        blnoLocalChargeDto.setLockIds(lockIds);
        return blnoLocalChargeDto;
    }

    private void deleteRedis(Collection<Long> calcuteIds, String imExType) {
        if (ExImTypeEnum.EX.getValue().equals(imExType)) {
            for (Long blnoId : calcuteIds) {
                redisUtils.deleteString(LocalChargeConstants.LOCALCHARGEREDISKEY_EX + blnoId);
            }
        } else {
            for (Long blnoId : calcuteIds) {
                redisUtils.deleteString(LocalChargeConstants.LOCALCHARGEREDISKEY_IM + blnoId);
            }
        }

    }

    /**
     * 调用营销接口获取费率
     *
     * @param blnoScnMap
     * @param blnoDoList
     * @param blnoCargoMap
     * @param blnoVslvoyMap
     * @param blnoMap
     * @param imExType
     * @param blnoCntrMap
     * @param currentPort
     * @return
     */
    private List<CalLocRespVo> calMktLocalCharge(Map<Long, List<BlnoScnDO>> blnoScnMap,
                                                 List<BlnoDO> blnoDoList,
                                                 Map<Long, List<BlnoCargoDO>> blnoCargoMap,
                                                 Map<Long, List<BlnoVslvoyDO>> blnoVslvoyMap,
                                                 Map<Long, List<BlnoDO>> blnoMap,
                                                 String imExType,
                                                 Map<Long, List<BlnoCntrDO>> blnoCntrMap,
                                                 String currentPort) {

        List<CalLocRespVo> calLocRespVoList = new ArrayList<>(128);
        String calType = CalculateTypeEnum.CALCULATE_LOCAL_CHARGE_EX.getValue();
        String optLogStatus = CalculateStatusEnum.SUCCESS.getValue();
        if (imExType.equals(ExImTypeEnum.IM.getValue())) {
            calType = CalculateTypeEnum.CALCULATE_LOCAL_CHARGE_IM.getValue();
        }
        String failMessage = "";
        // 构建请求营销接口参数，主要是询价
        List<CalLocReqVo> calLocReqVoList = new ArrayList<>(128);
        for (BlnoDO blnoDO : blnoDoList) {
            CalLocReqVo calLocReqVo = new CalLocReqVo();
            BlnoCargoDO blnoCargoDo = new BlnoCargoDO();
            blnoCargoDo = blnoCargoMap.get(blnoDO.getId()).get(0);
            List<BlnoVslvoyDO> blnoVslvoy = blnoVslvoyMap.get(blnoDO.getId());
            String laneCode = "";
            if (ExImTypeEnum.EX.getValue().equals(imExType)) {
                laneCode = CostChargeUtil.getExLaneCode(blnoVslvoy, currentPort);
            } else {
                laneCode = CostChargeUtil.getImLaneCode(blnoVslvoy, currentPort);
            }
            calLocReqVo = this.buildCalFrtReqVo(blnoDO, blnoCntrMap.get(blnoDO.getId()), blnoCargoDo, blnoVslvoyMap.get(blnoDO.getId()), blnoScnMap.get(blnoDO.getId()), laneCode, imExType, currentPort);
            calLocReqVoList.add(calLocReqVo);
        }
        if (ExImTypeEnum.EX.getValue().equals(imExType)) {
            CommonResult<List<CalLocRespVo>> commonResult = rateTransfer.batchCalPorLocal(calLocReqVoList);
            if (commonResult.getCode() == 0) {
                calLocRespVoList = commonResult.getData();
            } else {
                failMessage = commonResult.getMsg();
                optLogStatus = CalculateStatusEnum.EXCEPTION.getValue();
                blnoCalculateOperateService.saveCalLog(blnoMap, calLocReqVoList, calLocRespVoList, blnoCntrMap, calType, optLogStatus, failMessage, imExType);
                throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_ERROR);
            }
        } else {
            CommonResult<List<CalLocRespVo>> commonResult = rateTransfer.batchCalDesLocal(calLocReqVoList);
            if (commonResult.getCode() == 0) {
                calLocRespVoList = commonResult.getData();
            } else {
                failMessage = commonResult.getMsg();
                optLogStatus = CalculateStatusEnum.EXCEPTION.getValue();
                blnoCalculateOperateService.saveCalLog(blnoMap, calLocReqVoList, calLocRespVoList, blnoCntrMap, calType, optLogStatus, failMessage, imExType);
                throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_ERROR);
            }
        }
        //保存日志
        blnoCalculateOperateService.saveCalLog(blnoMap, calLocReqVoList, calLocRespVoList, blnoCntrMap, calType, optLogStatus, failMessage, imExType);
        return calLocRespVoList;
    }

    /**
     * local charge 费用计算，一个提单可以同时存在按票和按箱两种计算方式
     * 一个提单是单货类
     * local charge detail 的数据如果是按票计算的detail表中每个费目一条记录，按箱计算的话是每个箱子的每个费目一条记录
     * local charge 的数据是按照charge_code,per、cntr_type、cntr_size、cargo_type、rate、currency、payer、local_port、ImExType、blid维度进行汇总的，这里是批量处理所以还要增加提单主键，blid
     * 天津港的费用计算，电放费（GDS和TRF）是按照提单信息计算，天津其他收费科目是按照仓单数量计算，其他港口的费用计算都是按照提单数量计算
     *
     * @param blnoMap          提单基础信息
     * @param blnoCntrMap      箱信息
     * @param blnoCargoMap     货物信息
     * @param calLocRespVoList 营销的费用信息
     * @param imExtype         进出口
     * @param blnoVslvoyMap    船名航次
     */
    private List<BlnoLocalChargeResultVO> calcute(Map<Long, List<BlnoDO>> blnoMap,
                                                  Map<Long, List<BlnoCntrDO>> blnoCntrMap,
                                                  Map<Long, List<BlnoCargoDO>> blnoCargoMap,
                                                  List<CalLocRespVo> calLocRespVoList,
                                                  String imExtype,
                                                  Map<Long, List<BlnoVslvoyDO>> blnoVslvoyMap,
                                                  String currentPort) {
        Locale currentLocale = LocaleContextHolder.getLocale();
        List<BlnoLocalChargeResultVO> blnoLocalChargeResultVos = new ArrayList<>(128);
        //将返回的数据处理成map<Long,List<CalLocRespVo>>的形式避免四层循环,这里不会存在相同的blid相同的数据但是还是指定了处理方式,存在相同的数据时全部加入到到处理的记录中
        Map<Long, List<CalLocRateUnitRespVo>> calLocRateUnitRespVo = calLocRespVoList.stream().collect(Collectors.toMap(CalLocRespVo::getBlnoId, CalLocRespVo::getRateUnits, (oldValue, newValue) -> {
            oldValue.addAll(newValue);
            return oldValue;
        }));
        //按照提单的维度处理local charge表和local charge detail 表的数据
        List<BlnoLocalChargeDO> blnoLocalChargeDoList = new ArrayList<>(64);
        List<BlnoLocalChargeDetailDO> blnoLocalChargeDetailDoList = new ArrayList<>(128);
        //提单循环
        for (Map.Entry<Long, List<CalLocRateUnitRespVo>> entry : calLocRateUnitRespVo.entrySet()) {
            //获取提单对应提单、货、箱、费目费率信息
            BlnoDO blnoDO = blnoMap.get(entry.getKey()).get(0);
            BlnoCargoDO blnoCargoDO = blnoCargoMap.get(entry.getKey()).get(0);
            List<BlnoCntrDO> blnoCntrDoList = blnoCntrMap.get(entry.getKey());
            List<BlnoVslvoyDO> blnoVslvoyDoList = blnoVslvoyMap.get(entry.getKey());
            if (CollectionUtils.isEmpty(entry.getValue())) {
                BlnoLocalChargeResultVO blnoLocalChargeResultVo = BlnoLocalChargeResultVO.builder().blNo(blnoDO.getBlNo())
                        .msg(i18nsUtil.getMessage(BlnoLocalChargeErrorCodeConstants.MKT_PAYMENT_ERROR.getCode(), currentLocale))
                        .type(CostTypeEnum.LOCAL_CHARGE.getValue()).build();
                blnoLocalChargeResultVos.add(blnoLocalChargeResultVo);
                break;
            }
            //计费方式循环，一个提单有多种计费方式
            outBreak:
            {
                for (CalLocRateUnitRespVo locRateUnitRespVo : entry.getValue()) {
                    if (null == locRateUnitRespVo || StringUtils.isBlank(locRateUnitRespVo.getCntrSizeType())) {
                        BlnoLocalChargeResultVO blnoLocalChargeResultVo = BlnoLocalChargeResultVO.builder().blNo(blnoDO.getBlNo())
                                .msg(i18nsUtil.getMessage(BlnoLocalChargeErrorCodeConstants.MKT_PAYMENT_TYPE_NULL.getCode(), currentLocale))
                                .type(CostTypeEnum.LOCAL_CHARGE.getValue()).build();
                        blnoLocalChargeResultVos.add(blnoLocalChargeResultVo);
                        break outBreak;
                    }
                    //判断计费模式，然后在取对用的
                    if (LocalChargeConstants.CALCUTTYPE.equals(locRateUnitRespVo.getCntrSizeType())) {
                        if (CollectionUtils.isEmpty(locRateUnitRespVo.getFeeDetails())) {
                            BlnoLocalChargeResultVO blnoLocalChargeResultVo = BlnoLocalChargeResultVO.builder().blNo(blnoDO.getBlNo())
                                    .msg(i18nsUtil.getMessage(BlnoLocalChargeErrorCodeConstants.MKT_PAYMENT_RATE_NULL.getCode(), currentLocale))
                                    .type(CostTypeEnum.LOCAL_CHARGE.getValue()).build();
                            blnoLocalChargeResultVos.add(blnoLocalChargeResultVo);
                            break outBreak;
                        }
                        //费目和费率循环
                        for (FeeRateVo feeRateVo : locRateUnitRespVo.getFeeDetails()) {
                            //初始化一些默认值
                            BlnoLocalChargeDetailDO blnoLocalChargeDetailDo = this.initBlnoLocalChargeDetailDo(blnoDO, blnoCargoDO, locRateUnitRespVo, feeRateVo, "", imExtype, blnoVslvoyDoList, currentPort);
                            //判断是否是天津港的数据并且是出口的数据,
                            if (LocalChargeConstants.CNTXG.equals(blnoDO.getCurrentPort()) && ExImTypeEnum.EX.getValue().equals(imExtype)) {
                                //天津港电放费是按照提单计算(这里不用在考虑是否是目的港提单，因为只有目的港提单的数据才能被查出来参与计算)，天津港其他费目按照仓单的数量计算
                                if (LocalChargeConstants.GDS.equals(feeRateVo.getFeeCode()) || LocalChargeConstants.TRF.equals(feeRateVo.getFeeCode())) {
                                    blnoLocalChargeDetailDo.setQuantity(new BigDecimal(1));
                                } else {
                                    //按照仓单计算，查询提单对应的仓单数据,条件是仓单已确认的
                                    LambdaQueryWrapperX<ManifestDO> lambdaQueryWrapperX = new LambdaQueryWrapperX<>();
                                    lambdaQueryWrapperX.eqIfPresent(ManifestDO::getBookingNo, blnoDO.getBookingNo()).eq(ManifestDO::getConfirmStatus, true);
                                    Long count = manifestMapper.selectCount(lambdaQueryWrapperX);
                                    blnoLocalChargeDetailDo.setQuantity(new BigDecimal(count));
                                }
                            } else {
                                blnoLocalChargeDetailDo.setQuantity(new BigDecimal(1));
                            }
                            //计算金额以及处理日本的保留整数
                            blnoLocalChargeDetailDo.setAmount(CommonUtil.decimalMulHalfUp(blnoLocalChargeDetailDo.getQuantity(), blnoLocalChargeDetailDo.getRate(), blnoLocalChargeDetailDo.getCurrency()));
                            blnoLocalChargeDetailDoList.add(blnoLocalChargeDetailDo);
                        }
                    } else {
                        //按箱计算
                        for (BlnoCntrDO blnoCntrDO : blnoCntrDoList) {
                            //根据箱型尺寸箱主查找下面的费目和费率信息
                            if (blnoCntrDO.getCntrType().equals(locRateUnitRespVo.getCntrType())
                                    && blnoCntrDO.getCntrSize().equals(String.valueOf(locRateUnitRespVo.getCntrSize()))
                                    && blnoCntrDO.getCntrOwner().equals(locRateUnitRespVo.getCntrOwnerType())) {
                                if (CollectionUtils.isEmpty(locRateUnitRespVo.getFeeDetails())) {
                                    BlnoLocalChargeResultVO blnoLocalChargeResultVo = BlnoLocalChargeResultVO.builder().blNo(blnoDO.getBlNo())
                                            .msg(i18nsUtil.getMessage(BlnoLocalChargeErrorCodeConstants.MKT_PAYMENT_RATE_NULL.getCode(), currentLocale))
                                            .type(CostTypeEnum.LOCAL_CHARGE.getValue()).build();
                                    blnoLocalChargeResultVos.add(blnoLocalChargeResultVo);
                                    break outBreak;
                                }
                                for (FeeRateVo feeRateVo : locRateUnitRespVo.getFeeDetails()) {
                                    //初始化一些默认值
                                    BlnoLocalChargeDetailDO blnoLocalChargeDetailDo = this.initBlnoLocalChargeDetailDo(blnoDO, blnoCargoDO, locRateUnitRespVo, feeRateVo, blnoCntrDO.getCntrNo(), imExtype, blnoVslvoyDoList, currentPort);
                                    //计算金额以及处理日本的保留整数
                                    blnoLocalChargeDetailDo.setAmount(CommonUtil.decimalMulHalfUp(blnoLocalChargeDetailDo.getQuantity(), blnoLocalChargeDetailDo.getRate(), blnoLocalChargeDetailDo.getCurrency()));
                                    blnoLocalChargeDetailDoList.add(blnoLocalChargeDetailDo);
                                }
                            }
                        }
                    }

                }
            }
        }
        //处理主表汇总 local charge
        blnoLocalChargeDoList = this.initBlnoLocalChargeDetailDo(blnoLocalChargeDetailDoList);
        //将所有已经计算成功的提单塞回到结果中
        if (CollectionUtils.isNotEmpty(blnoLocalChargeDoList)) {
            blnoLocalChargeResultVos.addAll(this.dealResult(blnoLocalChargeDoList));
        }
        //处理红冲以及添加保存
        this.redImpact(blnoLocalChargeDoList, blnoLocalChargeDetailDoList, imExtype);
        return blnoLocalChargeResultVos;
    }

    private List<BlnoLocalChargeResultVO> dealResult(List<BlnoLocalChargeDO> blnoLocalChargeDoList) {
        List<BlnoLocalChargeResultVO> blnoLocalChargeResultVos = new ArrayList<>(128);
        List<Long> ids = blnoLocalChargeDoList.stream().map(BlnoLocalChargeDO::getBlnoId).collect(Collectors.toList());
        LambdaQueryWrapperX<BlnoDO> lambdaQueryWrapperX = new LambdaQueryWrapperX<>();
        lambdaQueryWrapperX.inIfPresent(BlnoDO::getId, ids).eq(BlnoDO::getValid, SiValidEnum.VALID.getValue());
        List<BlnoDO> blnoDoList = blnoMapper.selectList(lambdaQueryWrapperX);
        blnoLocalChargeResultVos = blnoDoList.stream().map(item -> {
            return BlnoLocalChargeResultVO.builder().blNo(item.getBlNo()).type(CostTypeEnum.LOCAL_CHARGE.getValue()).msg(SUCCESS).build();
        }).collect(Collectors.toList());
        return blnoLocalChargeResultVos;
    }

    /**
     * 1.逻辑删除所有的明细
     * 2.将所有新计算的明细数据重新插入到数据库
     * 3.查出所有的已计算过的提单的费用数据不含手动添加的
     * 4.计算过提单数据包括红冲+被红冲的数据，已报财务数据，未报财务数据
     * 4.1红冲+被红冲的数据无需做处理
     * 4.2已报财务的数据分为以下几种情况
     * 4.2.1已报财务重新计算的结果中没有这条记录需要红冲
     * 4.2.2已报财务重新计算的结果相同无需处理
     * 4.2.3已报财务重新计算的结果中存在这条数据且金额不一致需要红冲
     * 4.3未报财务数据逻辑删除然后重新插入
     * 5.本次计算新增的记录直接插入
     * 6.存在不存在的维度是charge_code,per、cntr_type、cntr_size、cargo_type、rate、currency、local_port、ImExType
     * 7.红冲条件：payer和rate两个有一个发生变化都要红冲
     * 8.手动添加的记录不做处理
     *
     * @param newLocalChargeDoList
     * @param newLocalChargeDetailDoList
     */
    public void redImpact(List<BlnoLocalChargeDO> newLocalChargeDoList, List<BlnoLocalChargeDetailDO> newLocalChargeDetailDoList, String imExType) {
        List<Long> chargeBlIds = newLocalChargeDoList.stream().map(BlnoLocalChargeDO::getBlnoId).collect(Collectors.toList());
        LambdaQueryWrapperX<BlnoLocalChargeDetailDO> lambdaQueryWrapperX = new LambdaQueryWrapperX<>();
        lambdaQueryWrapperX.inIfPresent(BlnoLocalChargeDetailDO::getBlnoId, chargeBlIds)
                .eq(BlnoLocalChargeDetailDO::getImExType, imExType);
        blnoLocalChargeDetailMapper.delete(lambdaQueryWrapperX);

        //查询出所有被计算过的提单的历史计算结果
        LambdaQueryWrapperX<BlnoLocalChargeDO> localChargeLambdaQueryWrapper = new LambdaQueryWrapperX<>();
        localChargeLambdaQueryWrapper.inIfPresent(BlnoLocalChargeDO::getBlnoId, chargeBlIds)
                .eq(BlnoLocalChargeDO::getRecordType, RecordTypeEnum.AUTO.getValue())
                .eq(BlnoLocalChargeDO::getImExType, imExType);
        List<BlnoLocalChargeDO> oldLocalChargeDoList = blnoLocalChargeMapper.selectList(localChargeLambdaQueryWrapper);

        if (CollectionUtils.isNotEmpty(oldLocalChargeDoList)) {
            //所有红冲的数据
            List<BlnoLocalChargeDO> oldRedAllRedLocalCharge = oldLocalChargeDoList.stream().filter(item -> RedStatusEnum.RED.getValue().equals(item.getRedStatus())).collect(Collectors.toList());
            //已报财务历史计算的数据
            List<BlnoLocalChargeDO> oldToAccountLocalCharge = new ArrayList<>(128);
            //未报财务历史计算的数据
            List<BlnoLocalChargeDO> oldNotToAccountLocalCharge = new ArrayList<>(128);
            //产生的红冲的记录数据
            List<BlnoLocalChargeDO> newRedAllLocalCharge = new ArrayList<>(128);
            //需要更新为红冲标记的数据记录
            List<BlnoLocalChargeDO> redBlnoInfo = new ArrayList<>(128);
            //从历史计算结果中将红冲数据剔除掉
            if (CollectionUtils.isNotEmpty(oldRedAllRedLocalCharge)) {
                oldLocalChargeDoList.removeAll(oldRedAllRedLocalCharge);
            }
            //过滤出已经报财务的历史数据
            oldToAccountLocalCharge = oldLocalChargeDoList.stream().filter(BlnoLocalChargeDO::getToAccount).collect(Collectors.toList());
            oldNotToAccountLocalCharge = oldLocalChargeDoList.stream().filter(item -> !item.getToAccount()).collect(Collectors.toList());
            //处理已经报财务的情况，报财务不存在，报财务存在且金额一致，报财务存在金额不一致
            if (CollectionUtils.isNotEmpty(oldToAccountLocalCharge)) {
                Map<Long, List<BlnoLocalChargeDO>> newLocalChargeMap = newLocalChargeDoList.stream().collect(Collectors.groupingBy(BlnoLocalChargeDO::getBlnoId));
                for (BlnoLocalChargeDO oldToAccount : oldToAccountLocalCharge) {
                    //newBlIdLocalChargeList 不会为空无需判断空
                    List<BlnoLocalChargeDO> newBlIdLocalChargeList = newLocalChargeMap.get(oldToAccount.getBlnoId());
                    BlnoLocalChargeDO newLocalChargeExist = new BlnoLocalChargeDO();
                    boolean flag = false;
                    for (BlnoLocalChargeDO newLocalCharge : newBlIdLocalChargeList) {
                        //判断已经报财务的是否存在,flag = true时存在,false说明不存在需要红冲
                        flag = this.checkLocalCharge(oldToAccount, newLocalCharge);
                        if (flag) {
                            newLocalChargeExist = newLocalCharge;
                            break;
                        }
                    }
                    if (flag) {
                        //存在判断金额和payer是否一致，
                        if (newLocalChargeExist.getPayer().equals(oldToAccount.getPayer()) && newLocalChargeExist.getAmount().compareTo(oldToAccount.getAmount()) == 0) {
                            newLocalChargeDoList.remove(newLocalChargeExist);
                        } else {
                            newRedAllLocalCharge.add(this.redLocalCharge(oldToAccount));
                            redBlnoInfo.add(oldToAccount);
                        }
                    } else {
                        //不存在直接红冲
                        newRedAllLocalCharge.add(this.redLocalCharge(oldToAccount));
                        redBlnoInfo.add(oldToAccount);
                    }
                }
            }
            //删除所有未报财务的数据
            if (CollectionUtils.isNotEmpty(oldNotToAccountLocalCharge)) {
                List<Long> deleteIds = oldNotToAccountLocalCharge.stream().map(BlnoLocalChargeDO::getId).collect(Collectors.toList());
                LambdaQueryWrapperX<BlnoLocalChargeDO> chargeDoLambdaQueryWrapperX = new LambdaQueryWrapperX<>();
                chargeDoLambdaQueryWrapperX.inIfPresent(BlnoLocalChargeDO::getId, deleteIds);
                blnoLocalChargeMapper.delete(chargeDoLambdaQueryWrapperX);
            }
            //更新被红冲记录的标识
            if (CollectionUtils.isNotEmpty(redBlnoInfo)) {
                for (BlnoLocalChargeDO redBlno : redBlnoInfo) {
                    redBlno.setRedStatus(RedStatusEnum.RED.getValue());
                    redBlno.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
                    redBlno.setUpdateTime(LocalDateTime.now());
                }
                blnoLocalChargeMapper.updateBatch(redBlnoInfo);
            }
            //将所有红冲的记录插入到新计算的结果中
            if (CollectionUtils.isNotEmpty(newRedAllLocalCharge)) {
                newLocalChargeDoList.addAll(newRedAllLocalCharge);
            }
        }

        blnoLocalChargeDetailMapper.insertBatch(newLocalChargeDetailDoList);
        if (CollectionUtils.isNotEmpty(newLocalChargeDoList)) {
            blnoLocalChargeMapper.insertBatch(newLocalChargeDoList);
        }
    }


    /**
     * 红冲数据
     *
     * @param oldToAccount 红冲数据
     * @return BlnoLocalChargeDO
     */
    private BlnoLocalChargeDO redLocalCharge(BlnoLocalChargeDO oldToAccount) {

        return BlnoLocalChargeDO.builder()
                .blnoId(oldToAccount.getBlnoId())
                .imExType(oldToAccount.getImExType())
                .chargeCode(oldToAccount.getChargeCode())
                .per(oldToAccount.getPer())
                .cntrType(oldToAccount.getCntrType())
                .cntrSize(oldToAccount.getCntrSize())
                .cargoType(oldToAccount.getCargoType())
                .quantity(oldToAccount.getQuantity())
                .rate(oldToAccount.getRate())
                .amount(oldToAccount.getAmount().negate())
                .payer(oldToAccount.getPayer())
                .currency(oldToAccount.getCurrency())
                .toAccount(false)
                .recordType(RecordTypeEnum.AUTO.getValue())
                .localPort(oldToAccount.getLocalPort())
                .redStatus(RedStatusEnum.RED.getValue())
                .originalId(oldToAccount.getId()).build();
    }

    /**
     * 判断是否存在 charge_code,per、cntr_type、cntr_size、cargo_type、currency、local_port、ImExType,存在一致返回true,否则返回false
     *
     * @param oldLocalCharge 数据库中的计算数据
     * @param newLocalCharge 新计算生成的数据
     * @return Boolean
     */
    private Boolean checkLocalCharge(BlnoLocalChargeDO oldLocalCharge, BlnoLocalChargeDO newLocalCharge) {

        if (!oldLocalCharge.getChargeCode().equals(newLocalCharge.getChargeCode())) {
            return false;
        }
        if (!oldLocalCharge.getPer().equals(newLocalCharge.getPer())) {
            return false;
        }
        if (!oldLocalCharge.getCntrType().equals(newLocalCharge.getCntrType())) {
            return false;
        }
        if (!oldLocalCharge.getCntrSize().equals(newLocalCharge.getCntrSize())) {
            return false;
        }
        if (!oldLocalCharge.getCargoType().equals(newLocalCharge.getCargoType())) {
            return false;
        }
        if (!oldLocalCharge.getCurrency().equals(newLocalCharge.getCurrency())) {
            return false;
        }
        if (!oldLocalCharge.getLocalPort().equals(newLocalCharge.getLocalPort())) {
            return false;
        }
        return oldLocalCharge.getImExType().equals(newLocalCharge.getImExType());
    }

    /**
     * 按照charge_code,per、cntr_type、cntr_size、cargo_type、rate、currency、payer、local_port、Payment、ImExType、blid维度进行分类汇总的
     *
     * @param blnoLocalChargeDetailDoList
     * @return
     */
    private List<BlnoLocalChargeDO> initBlnoLocalChargeDetailDo(List<BlnoLocalChargeDetailDO> blnoLocalChargeDetailDoList) {
        List<BlnoLocalChargeDO> blnoLocalChargeDoList = new ArrayList<>(64);
        Map<BlnoLocalChargeTempVO, List<BlnoLocalChargeDetailDO>> blnoLocalChargeTempVoListMap = blnoLocalChargeDetailDoList.stream().collect(Collectors.groupingBy(item ->
                new BlnoLocalChargeTempVO(item.getBlnoId(),
                        item.getChargeCode(),
                        item.getPer(),
                        item.getCntrType(),
                        item.getCntrSize(),
                        item.getCargoType(),
                        item.getRate(),
                        item.getCurrency(),
                        item.getPayer(),
                        item.getLocalPort(),
                        item.getImExType(),
                        item.getPayment())
        ));
        for (Map.Entry<BlnoLocalChargeTempVO, List<BlnoLocalChargeDetailDO>> entry : blnoLocalChargeTempVoListMap.entrySet()) {
            BlnoLocalChargeDO blnoLocalChargeDo = new BlnoLocalChargeDO();
            blnoLocalChargeDo = BlnoLocalChargeConvert.INSTANCE.convertblnoLocalChargeDo(entry.getKey());
            blnoLocalChargeDo.setRecordType(RecordTypeEnum.AUTO.getValue());
            blnoLocalChargeDo.setRedStatus(RedStatusEnum.NOTRED.getValue());
            blnoLocalChargeDo.setToAccount(false);
            blnoLocalChargeDo.setQuantity(entry.getValue().size());
            //汇总金额
            BigDecimal amount = entry.getValue().stream().map(item -> Optional.ofNullable(item.getAmount()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            blnoLocalChargeDo.setAmount(amount);
            blnoLocalChargeDoList.add(blnoLocalChargeDo);
        }
        return blnoLocalChargeDoList;
    }

    /**
     * 构建local charge detail
     *
     * @param blnoDo            提单基础信息
     * @param blnoCargoDo       货物信息
     * @param locRateUnitRespVo 费目
     * @param feeRateVo         费率
     * @param containerNo       箱号
     * @param imExType          进出口
     * @param blnoVslvoyDoList  船名航次
     * @return BlnoLocalChargeDetailDO
     */
    private BlnoLocalChargeDetailDO initBlnoLocalChargeDetailDo(BlnoDO blnoDo,
                                                                BlnoCargoDO blnoCargoDo,
                                                                CalLocRateUnitRespVo locRateUnitRespVo,
                                                                FeeRateVo feeRateVo,
                                                                String containerNo,
                                                                String imExType,
                                                                List<BlnoVslvoyDO> blnoVslvoyDoList,
                                                                String currentPort) {
        BlnoLocalChargeDetailDO blnoLocalChargeDetailDo = new BlnoLocalChargeDetailDO();
        blnoLocalChargeDetailDo.setBlnoId(blnoDo.getId());
        blnoLocalChargeDetailDo.setImExType(imExType);
        blnoLocalChargeDetailDo.setChargeCode(feeRateVo.getFeeCode());
        //按票计算时没有箱号
        blnoLocalChargeDetailDo.setContainerNo(containerNo);
        blnoLocalChargeDetailDo.setPer(LocalChargeConstants.CALCUTTYPE.equals(locRateUnitRespVo.getCntrSizeType()) ? CalculatePerEnum.BILL.getValue() : CalculatePerEnum.CNTR.getValue());
        blnoLocalChargeDetailDo.setCntrType(locRateUnitRespVo.getCntrType());
        blnoLocalChargeDetailDo.setCntrSize(String.valueOf(locRateUnitRespVo.getCntrSize()));
        blnoLocalChargeDetailDo.setCargoType(blnoCargoDo.getCargoType());
        blnoLocalChargeDetailDo.setQuantity(new BigDecimal(1));
        blnoLocalChargeDetailDo.setRate(feeRateVo.getRate());
        blnoLocalChargeDetailDo.setToAccount(false);
        blnoLocalChargeDetailDo.setCurrency(feeRateVo.getCur());
        blnoLocalChargeDetailDo.setPayment(feeRateVo.getPayTerm());
        String payer = "";
        if (StringUtils.isBlank(feeRateVo.getPayerCode())) {
            payer = blnoDo.getBkpCode();
        } else {
            payer = feeRateVo.getPayerCode();
        }
        blnoLocalChargeDetailDo.setPayer(payer);
        blnoLocalChargeDetailDo.setRecordType(RecordTypeEnum.AUTO.getValue());
        blnoLocalChargeDetailDo.setLocalPort(currentPort);
        return blnoLocalChargeDetailDo;
    }


    /**
     * 构建询价参数
     *
     * @param blnoDO         提单信息
     * @param blnoCntrDoList
     * @param blnoCargoDO    货信息
     * @param vslvoyList     航线
     * @param scnList        收发通
     * @return
     */
    private CalLocReqVo buildCalFrtReqVo(BlnoDO blnoDO,
                                         List<BlnoCntrDO> blnoCntrDoList,
                                         BlnoCargoDO blnoCargoDO,
                                         List<BlnoVslvoyDO> vslvoyList,
                                         List<BlnoScnDO> scnList,
                                         String laneCode,
                                         String imExType,
                                         String currentPort) {
        //和营销负责人确认，无需传4p，营销根据routeId自己处理
        CalLocReqVo calLocReqVo = BlnoFreightConvert.INSTANCE.convertCalLocReqVo(blnoDO);
        calLocReqVo.setQueryId(IdUtil.getSnowflakeNextIdStr());
        calLocReqVo.setLaneCode(laneCode);
        List<String> vesselVoys = new ArrayList<>(4);
        ImExVslvoyAge imExVslvoyAge = new ImExVslvoyAge();
        //获取进口或者出口航次
        if (ExImTypeEnum.EX.getValue().equalsIgnoreCase(imExType)) {
            imExVslvoyAge = CostChargeUtil.getExVslvoyAge(vslvoyList, currentPort);
        } else {
            imExVslvoyAge = CostChargeUtil.getImVslvoyAge(vslvoyList, currentPort);
        }
        vesselVoys.add(imExVslvoyAge.getVesselCode());
        LocalDateTime etdDate = costChargeUtil.getBusinessDate(currentPort, imExVslvoyAge.getVoyage(), laneCode, vesselVoys);
        calLocReqVo.setBusinessDate(etdDate);
        calLocReqVo.setQueryDate(LocalDateTime.now());
        calLocReqVo.setUserId(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        scnList.forEach(blno -> {
            if (PartyTypeCodeEnum.SHIPPER.getValue().equals(blno.getPartyTypeCode())) {
                calLocReqVo.setShipperCode(blno.getCsmCode());
            }
            if (PartyTypeCodeEnum.CONSIGNEE.getValue().equals(blno.getPartyTypeCode())) {
                calLocReqVo.setConsigneeCode(blno.getCsmCode());
            }
            if (PartyTypeCodeEnum.NOTIFY.getValue().equals(blno.getPartyTypeCode())) {
                calLocReqVo.setNotifyCode(blno.getCsmCode());
            }
        });
        calLocReqVo.setShippingTermKey(blnoDO.getShippingTerm());
        calLocReqVo.setShippingTermValue(null);
        calLocReqVo.setFeePreferPayTerm(buildFeePreferPayMap(blnoDO.getId()));
        List<CalLocRateUnitReqVo> rateUnitList = buildRateUnitList(blnoDO, blnoCntrDoList, blnoCargoDO);
        calLocReqVo.setRateUnits(rateUnitList);
        return calLocReqVo;
    }

    /**
     * 构建请求营销的接口数据信息
     *
     * @param blnoDO         提单信息
     * @param blnoCntrDOList 箱信息
     * @param blnoCargoDO    货信息
     * @return
     */
    private List<CalLocRateUnitReqVo> buildRateUnitList(BlnoDO blnoDO, List<BlnoCntrDO> blnoCntrDOList, BlnoCargoDO blnoCargoDO) {
        List<CalLocRateUnitReqVo> rateUnitList = new ArrayList<>();
        if (CollectionUtils.isEmpty(blnoCntrDOList)) {
            return rateUnitList;
        }
        List<BlnoCntrDO> blnoCntrDoDeDupList = deDuplicated(blnoCntrDOList);
        for (BlnoCntrDO cntr : blnoCntrDoDeDupList) {
            CalLocRateUnitReqVo rateUnit = new CalLocRateUnitReqVo();
            rateUnit.setCntrSizeType(cntr.getCntrSize() + cntr.getCntrType());
            rateUnit.setCargoType(blnoCargoDO.getCargoType());
            rateUnit.setCntrType(cntr.getCntrType());
            rateUnit.setCntrSize(Integer.valueOf(cntr.getCntrSize()));
            rateUnit.setCntrStatus(blnoDO.getCntrStatus());
            rateUnit.setCntrOwnerType(cntr.getCntrOwner());
            rateUnit.setHsCode(blnoCargoDO.getHsCode());
            rateUnit.setCargoName(blnoCargoDO.getCommodityEn());
            rateUnit.setOog("");
            rateUnitList.add(rateUnit);
        }
        return rateUnitList;
    }

    /**
     * 按照箱型尺寸对箱型做处理，同一个箱型尺寸进行合并，同一个提单下箱信息要不是重箱F或者空箱E
     *
     * @param list
     * @return
     */
    private List<BlnoCntrDO> deDuplicated(List<BlnoCntrDO> list) {
        return list.stream().collect(Collectors.collectingAndThen(Collectors.toMap(cntr -> Arrays.asList(cntr.getCntrSize(), cntr.getCntrType()), cntr -> cntr, (existing, replacement) -> existing), map -> new ArrayList<>(map.values())));
    }

    private Map<String, String> buildFeePreferPayMap(Long blnoId) {
        List<BlnoPaymentDO> paymentDOList = blnoPaymentService.getBlnoPaymentsByBlnoId(blnoId);
        return paymentDOList.stream().collect(Collectors.toMap(BlnoPaymentDO::getChargeCode, BlnoPaymentDO::getDesignatedPayment));
    }

    @Override
    public List<BlnoLocalChargeUpdateReqVO> getBlnoChargeByBlnoId(Long blnoId) {
        LambdaQueryWrapper<BlnoLocalChargeDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BlnoLocalChargeDO::getBlnoId, blnoId);
        queryWrapper.orderByAsc(BlnoLocalChargeDO::getId);
        List<BlnoLocalChargeDO> blnoLocalChargeDos = blnoLocalChargeMapper.selectList(queryWrapper);
        if (CollectionUtil.isNotEmpty(blnoLocalChargeDos)) {
            List<String> codes = blnoLocalChargeDos.stream().map(BlnoLocalChargeDO::getChargeCode).collect(Collectors.toList());
            List<BlnoLocalChargeUpdateReqVO> blnoLocalChargeUpdateReqVos = BeanUtil.copyToList(blnoLocalChargeDos, BlnoLocalChargeUpdateReqVO.class);
            List<FinanceItemRespDTO> checkedData = null;
            try {
                FinanceItemListByCodeReqVO financeItemListByCodeReqVO = new FinanceItemListByCodeReqVO(codes);
                checkedData = financeItemApi.listByCode(financeItemListByCodeReqVO).getCheckedData();
            } catch (Exception e) {
                log.error(e.getMessage());
            }
            if (CollectionUtil.isNotEmpty(checkedData)) {
                Map<String, String> cnMap = checkedData.stream().collect(Collectors.toMap(FinanceItemRespDTO::getCode, FinanceItemRespDTO::getItemNameCn));
                Map<String, String> enMap = checkedData.stream().collect(Collectors.toMap(FinanceItemRespDTO::getCode, FinanceItemRespDTO::getItemNameEn));

                blnoLocalChargeUpdateReqVos.forEach(n -> {
                    n.setChargeNameCn(cnMap.get(n.getChargeCode()));
                    n.setChargeNameEn(enMap.get(n.getChargeCode()));
                });
            }
            return blnoLocalChargeUpdateReqVos;
        } else {
            return Collections.emptyList();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addLocalCharge(BlnoLocalChargeAddReqVO blnoLocalChargeAddReqVO) {
        BlnoLocalChargeDO blnoLocalChargeDO = BlnoLocalChargeConvert.INSTANCE.convertLocalChargeDo(blnoLocalChargeAddReqVO);
        blnoLocalChargeDO.setRecordType(RecordTypeEnum.MANUAL.getValue());
        blnoLocalChargeDO.setRedStatus(RedStatusEnum.NOTRED.getValue());
        blnoLocalChargeDO.setToAccount(false);
        blnoLocalChargeMapper.insert(blnoLocalChargeDO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateLocalCharge(BlnoLocalChargeDO blnoLocalChargeDo) {
        if (null == blnoLocalChargeDo.getId()) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_ID_ISNULL);
        }
        //判断是否报财务以及是否手动添加
        LambdaQueryWrapperX<BlnoLocalChargeDO> lambdaQueryWrapperX = new LambdaQueryWrapperX<>();
        lambdaQueryWrapperX.eqIfPresent(BlnoLocalChargeDO::getId, blnoLocalChargeDo.getId());
        BlnoLocalChargeDO blnoLocalCharge = blnoLocalChargeMapper.selectOne(lambdaQueryWrapperX);
        if (null == blnoLocalCharge) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_NOT_EXISTS);
        }
        if (!RecordTypeEnum.MANUAL.getValue().equals(blnoLocalCharge.getRecordType()) || blnoLocalCharge.getToAccount()) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_INFO_ERROR);
        }
        blnoLocalChargeDo.setImExType(blnoLocalCharge.getImExType());
        blnoLocalChargeDo.setToAccount(false);
        blnoLocalChargeDo.setRecordType(RecordTypeEnum.MANUAL.getValue());
        blnoLocalChargeDo.setUpdater(String.valueOf(SecurityFrameworkUtils.getLoginUser().getId()));
        blnoLocalChargeDo.setUpdateTime(LocalDateTime.now());
        blnoLocalChargeMapper.updateById(blnoLocalChargeDo);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void toAccount(String imExType, List<Long> blboIds) {
        LambdaQueryWrapperX<BlnoLocalChargeDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoLocalChargeDO::getToAccount, false)
                .eqIfPresent(BlnoLocalChargeDO::getImExType, imExType)
                .inIfPresent(BlnoLocalChargeDO::getBlnoId, blboIds);
        List<BlnoLocalChargeDO> blnoLocalChargeDoList = blnoLocalChargeMapper.selectList(queryWrapperX);
        if (CollectionUtils.isEmpty(blnoLocalChargeDoList)) {
            log.info("no Local charge need to send to account");
            return;
        }
        //redis加锁
        List<Long> ids = blnoLocalChargeDoList.stream().map(BlnoLocalChargeDO::getBlnoId).distinct().collect(Collectors.toList());
        List<Long> lockIds = new ArrayList<>();
        if (ExImTypeEnum.EX.getValue().equals(imExType)) {
            for (Long blnoId : ids) {
                if (redisUtils.checKeyExist(LocalChargeConstants.LOCALCHARGEREDISKEY_EX + blnoId)) {
                    lockIds.add(blnoId);
                } else {
                    redisUtils.setStringTimeOutByMinute(LocalChargeConstants.LOCALCHARGEREDISKEY_EX + blnoId, String.valueOf(blnoId), redisTimeOut);
                }
            }
        } else {
            for (Long blnoId : ids) {
                if (redisUtils.checKeyExist(LocalChargeConstants.LOCALCHARGEREDISKEY_IM + blnoId)) {
                    lockIds.add(blnoId);
                } else {
                    redisUtils.setStringTimeOutByMinute(LocalChargeConstants.LOCALCHARGEREDISKEY_IM + blnoId, String.valueOf(blnoId), redisTimeOut);
                }
            }
        }

        if (CollectionUtils.isNotEmpty(lockIds)) {
            ids.removeAll(lockIds);
        }
        if (CollectionUtils.isEmpty(ids)) {
            log.info(" all ids is lock");
            return;
        }
        List<BlnoDO> blnoDOList = blnoMapper.selectBatchIds(ids);
        List<BlnoVslvoyDO> blnoVslvoyDOList = blnoVslvoyService.getBlnoVslvoyBySiIdList(ids);
        DocBlnoBaseReqDTO docBlnoBaseReqDTO = buildToAccountInfo(blnoLocalChargeDoList, blnoDOList, blnoVslvoyDOList, imExType);
        Boolean flag = this.sendBlInfoToAccount(docBlnoBaseReqDTO);
        if (!flag) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_TOACCOUNT_ERROR);
        }
        blnoLocalChargeDoList.forEach(localCharge -> localCharge.setToAccount(true));
        blnoLocalChargeMapper.updateBatch(blnoLocalChargeDoList);
        //删除锁
        deleteRedis(ids, imExType);
    }

    /**
     * 发送财务
     *
     * @param blnoLocalChargeDoList
     * @param blnoDOList
     * @param blnoVslvoyDoAllList
     * @param imExType
     * @return
     */
    private DocBlnoBaseReqDTO buildToAccountInfo(List<BlnoLocalChargeDO> blnoLocalChargeDoList, List<BlnoDO> blnoDOList,
                                                 List<BlnoVslvoyDO> blnoVslvoyDoAllList, String imExType) {
        Map<Long, List<BlnoVslvoyDO>> vslvoyListMap = blnoVslvoyDoAllList.stream().collect(Collectors.groupingBy(BlnoVslvoyDO::getBlnoId));
        Map<Long, List<BlnoLocalChargeDO>> localChargeListMap = blnoLocalChargeDoList.stream().collect(Collectors.groupingBy(BlnoLocalChargeDO::getBlnoId));
        DocBlnoBaseReqDTO docBlnoBaseReq = new DocBlnoBaseReqDTO();
        List<DocBlnoCreateReqDTO> docBlnoList = new ArrayList<>();
        blnoDOList.forEach(blnoDO -> {
            DocBlnoCreateReqDTO blnoReq = BlnoLocalChargeConvert.INSTANCE.convertDocBlnoCreateReqDTO(blnoDO);
            blnoReq.setSender(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
            String formattedString = now.format(formatter);
            blnoReq.setSendTime(formattedString);
            blnoReq.setPayType(PayTypeEnum.INCOME.getValue());
            List<BlnoVslvoyDO> blnoVslvoyDoList = vslvoyListMap.getOrDefault(blnoDO.getId(), new ArrayList<>());
            List<DocBlnoVslvoyReqDTO> docBlnoVslvoyList = buildVslvoyReq(blnoVslvoyDoList);
            List<DocBlnoFreightInfoReqDTO> freightInfoList =
                    buildLocalChargeReq(localChargeListMap.getOrDefault(blnoDO.getId(), new ArrayList<>()));
            blnoReq.setDocBlnoVslvoyList(docBlnoVslvoyList);
            blnoReq.setFreightInfoList(freightInfoList);
            docBlnoList.add(blnoReq);
        });
        docBlnoBaseReq.setDocBlnoList(docBlnoList);
        return docBlnoBaseReq;
    }

    /**
     * @param blnoLocalChargeDoList
     * @return
     */
    private List<DocBlnoFreightInfoReqDTO> buildLocalChargeReq(List<BlnoLocalChargeDO> blnoLocalChargeDoList) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
        List<DocBlnoFreightInfoReqDTO> vslvoyReqList = new ArrayList<>();
        blnoLocalChargeDoList.forEach(localCharge -> {
            DocBlnoFreightInfoReqDTO freightReq = new DocBlnoFreightInfoReqDTO();
            freightReq.setFreightId(localCharge.getId());
            freightReq.setChargeCode(localCharge.getChargeCode());
            freightReq.setChargeCategory("LOCAL");
            freightReq.setLocalPortCode(localCharge.getLocalPort());
            freightReq.setCurrency(localCharge.getCurrency());
            freightReq.setAmount(localCharge.getAmount());
            freightReq.setPayment(localCharge.getPayment());
            freightReq.setPayerCode(localCharge.getPayer());
            freightReq.setRedFreightId(localCharge.getOriginalId());
            freightReq.setCreator(localCharge.getCreator());
            freightReq.setCreateTime(localCharge.getCreateTime().format(dateTimeFormatter));
            vslvoyReqList.add(freightReq);
        });
        return vslvoyReqList;
    }

    /**
     * @param blnoVslvoyDOList
     * @return
     */
    public static List<DocBlnoVslvoyReqDTO> buildVslvoyReq(List<BlnoVslvoyDO> blnoVslvoyDOList) {
        List<DocBlnoVslvoyReqDTO> vslvoyReqList = new ArrayList<>();
        blnoVslvoyDOList.forEach(vslvoy -> {
            DocBlnoVslvoyReqDTO vslvoyReq = BlnoFreightConvert.INSTANCE.convertVslvoyReq(vslvoy);
            vslvoyReqList.add(vslvoyReq);
        });
        return vslvoyReqList;
    }

    /**
     * 发送财务
     *
     * @param docBlnoBaseReq
     * @return
     */
    private Boolean sendBlInfoToAccount(DocBlnoBaseReqDTO docBlnoBaseReq) {
        CommonResult<Boolean> commonResult;
        try {
            commonResult = docBlnoApi.createBlNo(docBlnoBaseReq);
        } catch (Exception e) {
            log.error(GET_CNTR_TYPE_EXCEPTION, e);
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_TOACCOUNT_ERROR, e.toString());
        }

        if (commonResult == null || commonResult.getData() == null) {
            log.error(GET_CNTR_TYPE_EXCEPTION);
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_TOACCOUNT_NULL);
        }
        return commonResult.getCheckedData();
    }


    @Override
    public List<MultiBlnoLocalChargeRespVO> getMultiBlnoInfo(MultiBlnoLocalChargeReqVO reqVo) {

        boolean check = StringUtils.isBlank(reqVo.getBlNo()) && (StringUtils.isBlank(reqVo.getLinerVoyCode()) || StringUtils.isBlank(reqVo.getLinerVslCode()));
        if (check) {
            throw ServiceExceptionUtil.exception(BlnoLocalChargeErrorCodeConstants.BLNO_LOCAL_CHARGE_IS_NOT_ALL_NULL);
        }
        List<String> rejectStatuList = new ArrayList<>();
        rejectStatuList.add(SiStatusEnum.BookingDraft.getValue());
        rejectStatuList.add(SiStatusEnum.BookingProcessing.getValue());
        reqVo.setBlNoStatus(rejectStatuList);
        List<MultiBlnoLocalChargeRespVO> blnoLocalChargeRespVoList = new ArrayList<>(128);
        if (EmImTypeEnum.EXPORT.getValue().equals(reqVo.getImExType())) {
            blnoLocalChargeRespVoList = blnoMapper.getMultiExBlnoInfo(reqVo);
        } else {
            blnoLocalChargeRespVoList = blnoMapper.getMultiImBlnoInfo(reqVo);
        }
        if (CollectionUtils.isEmpty(blnoLocalChargeRespVoList)) {
            return Collections.emptyList();
        }

        List<String> codes = blnoLocalChargeRespVoList.stream().map(MultiBlnoLocalChargeRespVO::getBkpCode).collect(Collectors.toList());
        Map<String, String> companyNameMap = customerRpcService.getCompanyNameMap(codes);
        for (MultiBlnoLocalChargeRespVO multiBlnoLocalChargeRespVo : blnoLocalChargeRespVoList) {
            if (StringUtils.isNotBlank(multiBlnoLocalChargeRespVo.getBkpCode())) {
                multiBlnoLocalChargeRespVo.setBkpName(companyNameMap.getOrDefault(multiBlnoLocalChargeRespVo.getBkpCode(), ""));
            }
        }

        return blnoLocalChargeRespVoList;
    }
}
